package javax.generic.swing;

import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.generic.excel.ExcelService;
import javax.generic.persistance.AbstractPersistableEntity;
import javax.generic.persistance.GenericDao;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.springframework.util.StringUtils;

public abstract class AbstractForm<T extends AbstractPersistableEntity> extends JDialog {

	private static final long serialVersionUID = 7637887438417996868L;

	private static final Logger LOG = Logger.getLogger(AbstractForm.class);

	public static GenericDao dao;
	protected JToolBar bar;
	protected JTable table;

	protected boolean zoom = false;

	protected JLabel status;

	private Class<T> persistentEntity;

	protected T lastAdded;

	protected AbstractPersistableEntity fromDb;

	@SuppressWarnings("rawtypes")
	private static final Map<Class, List> cache = new HashMap<Class, List>();

	private ExcelService excelService;

	private JButton excelButton;

	public AbstractForm(Dialog owner, boolean zoom, Class<T> clazz) {
		super(owner, true);
		LOG.debug("creating window for class " + clazz.getSimpleName());
		this.persistentEntity = clazz;
		this.zoom = zoom;
		init();
	}

	private void init() {
		setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
		excelService = new ExcelService();
		int width = Toolkit.getDefaultToolkit().getScreenSize().width;
		int height = Toolkit.getDefaultToolkit().getScreenSize().height;
		setBounds(0, 0, width, height);
		LOG.debug("Creating form");

		bar = new JToolBar();
		bar.setRollover(true);

		bar.add(createButton("Pretraga", 'S', "search", new SearchAction()));
		bar.add(createButton("Osvezi prikaz", 'U', "refresh", new RefreshAction()));
		if (zoom) {
			bar.add(createButton("Prethodna forma", 'O', "zoom-pickup", new BackToFormAction()));
		} else {
			bar.addSeparator();

			bar.add(createButton("Unos", 'U', "add", new AddAction()));
			bar.add(createButton("Izmena", 'I', "edit", new EditAction()));
			bar.add(createButton("Brisanje", 'B', "remove", new DeleteAction()));
			bar.add(createButton("Kopiraj", 'K', "copy", new CopyAction()));
			bar.addSeparator();

			bar.add(createButton("Prvi slog", 'H', "first", new GotoFirstAction()));
			bar.add(createButton("Prethodni slog", 'P', "prev", new GotoPrevAction()));
			bar.add(createButton("Sledeci slog", 'S', "next", new GotoNextAction()));
			bar.add(createButton("Poslednji slog", 'P', "last", new GotoLastAction()));

			bar.addSeparator();
			excelButton = createButton("Excel", 'C', "export", new EXCELAction());
			bar.add(excelButton);
			bar.addSeparator();
			bar.addSeparator();
			bar.add(createButton("Umanji za poslednji dodati", 'm', "undo", new SubstractLastAction()));
		}

		add(bar, BorderLayout.NORTH);

		table = new JTable();

		JScrollPane scrollPane = new JScrollPane(table);
		table.setPreferredScrollableViewportSize(new Dimension(500, 70));
		add(scrollPane, BorderLayout.CENTER);

		table.setRowSelectionAllowed(true);
		table.setColumnSelectionAllowed(false);
		table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

		table.addKeyListener(new java.awt.event.KeyAdapter() {
			@Override
			public void keyPressed(java.awt.event.KeyEvent e) {
				if (java.awt.event.KeyEvent.VK_ESCAPE == e.getKeyCode()) {
					try {
						AbstractForm.this.finalize();
					} catch (Throwable e1) {
					}
				AbstractForm.this.dispose();
			}
			if (java.awt.event.KeyEvent.VK_ENTER == e.getKeyCode()) {
				T object = getSelected();
				if (object != null) {
					zoomFormBack(object);
				}
			}
		}
		});

		table.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (e.getClickCount() == 2) {
					if (zoom) {
						T object = getSelected();
						if (object != null) {
							zoomFormBack(object);
						}
					} else {
						editRow();
					}
				}
			}
		});

		status = new JLabel();
		add(status, BorderLayout.SOUTH);
		LOG.debug("Creating form done");
	}

	protected void setVerticalColumns() {
		TableCellRenderer headerRenderer = new VerticalTableHeaderCellRenderer();
		Enumeration<TableColumn> columns = table.getColumnModel().getColumns();
		while (columns.hasMoreElements()) {
			columns.nextElement().setHeaderRenderer(headerRenderer);
		}
	}

	private JButton createButton(String toolTipText, char mnemonic, String imgName, ActionListener actionListener) {
		JButton btUnos = new JButton();
		btUnos.setToolTipText(toolTipText);
		btUnos.setIcon(new ImageIcon(getClass().getResource("/images/" + imgName + ".gif")));
		btUnos.setFocusable(false);
		btUnos.addActionListener(actionListener);
		btUnos.setMnemonic(mnemonic);
		return btUnos;
	}

	protected void goLast() {
		int rowCount = table.getModel().getRowCount();
		if (rowCount > 0) table.setRowSelectionInterval(rowCount - 1, rowCount - 1);
		LOG.debug("Pozicioniranje na poslednji red tabele");
	}

	protected void goFirst() {
		int rowCount = table.getModel().getRowCount();
		if (rowCount > 0) table.setRowSelectionInterval(0, 0);
		LOG.debug("Pozicioniranje na prvi red tabele");
	}

	protected void goNext() {
		int rowSel = table.getSelectedRow();
		int rowCount = table.getModel().getRowCount();
		if (rowSel < rowCount - 1) table.setRowSelectionInterval(rowSel + 1, rowSel + 1);
		LOG.debug("Pozicioniranje na sledeći red tabele");
	}

	protected void goPrev() {
		int rowSel = table.getSelectedRow();
		if (rowSel > 0) table.setRowSelectionInterval(rowSel - 1, rowSel - 1);
		LOG.debug("Pozicioniranje na prethodni red tabele");
	}

	protected void deleteRow() {
		int index = table.getSelectedRow();
		Long id = (Long)table.getModel().getValueAt(index, 0);

		dao.removeById(id, persistentEntity);
		DefaultTableModel dtm = (DefaultTableModel)table.getModel();
		dtm.removeRow(index);
		dtm.fireTableDataChanged();
	}

	protected void editRow() {

		T object = getSelected();

		if (object == null) {
			showErrorMSg("Red nije selektovan");
			return;
		}

		EditEntityForm<T> editEntityForm = new EditEntityForm<T>(object, dao, table, this);
		editEntityForm.setTitle("Izmeni");
		editEntityForm.setVisible(true);
	}

	protected void addRow() {
		CreateEntityForm<T> createEntityForm = new CreateEntityForm<T>(getNew(), dao, table, this);
		createEntityForm.setTitle("Dodaj");
		createEntityForm.setVisible(true);
	}

	protected void copyRow() {
		T object = getSelected();
		object.setId(null);
		CreateEntityForm<T> createEntityForm = new CreateEntityForm<T>(object, dao, table, this);
		createEntityForm.setTitle("Napravi Kopiju");
		createEntityForm.setVisible(true);
	}

	protected int getStartRowNumber() {
		return 0;
	}

	protected HSSFWorkbook manageWorkbook(String filename) throws Exception {
		refreshView();
		return excelService.fillWorkbook(getAll(), filename, getStartRowNumber());
	}

	@SuppressWarnings("unchecked")
	protected void search() {

		VisualEditor<T> visualEditor = new VisualEditor<T>(getNew(), this) {

			private static final long serialVersionUID = 7799706849498520874L;

			@Override
			public void onOk(T object) {
				setNewItems(search(getAll(), object));
				lastAdded = object;
				super.onOk(object);
			}

			private List<T> search(final List<T> findAll, T object) {
				List<T> resultList = new ArrayList<T>();
				for (Object sourceObject : findAll) {
					if (localSearch(object, object.getClass(), sourceObject)) {
						resultList.add((T)sourceObject);
					}
				}
				return resultList;
			}
		};
		visualEditor.setTitle("Pretraga");
		visualEditor.setVisible(true);
	}

	private boolean localSearch(Object object, Class<?> clazz, Object resultObject) {

		Field[] fields = clazz.getDeclaredFields();
		boolean hit = true;
		Class<?> superclass = clazz.getSuperclass();

		if (!superclass.getSimpleName().equals("Object")) {
			hit = hit && localSearch(object, superclass, resultObject);
			if (!hit) {
				return false;
			}
		}

		for (Field field : fields) {
			if (!Modifier.isStatic(field.getModifiers())) {

				Method method;
				try {
					String methodName = "get" + StringUtils.capitalize(field.getName());
					clazz = ClassUtils.findExactClass(clazz, methodName);
					method = clazz.getDeclaredMethod(methodName);
					Object result = method.invoke(object);

					Object sourceValue = method.invoke(resultObject);
					hit = hit && (result == null ||
							(sourceValue != null && sourceValue.toString().toLowerCase().contains(result.toString().toLowerCase())));
					if (!hit) {
						break;
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return hit;
	}

	protected void zoomFormBack(T object) {
		AbstractForm.this.dispose();
	}

	@SuppressWarnings("unchecked")
	protected void refreshView() {
		LOG.info("loading all for " + persistentEntity.getName());
		@SuppressWarnings("rawtypes")
		List findAll = dao.findAll(persistentEntity);
		cache.put(persistentEntity, findAll);
		setNewItems(findAll);
	}

	protected void setNewItems(List<T> items) {
		DefaultTableModel dtm = (DefaultTableModel)table.getModel();
		dtm.setRowCount(0);

		for (T proizvod : items) {
			dtm.addRow(proizvod.getRow());
		}
		dtm.fireTableDataChanged();
		updateStatus(items);
		goFirst();
	}

	@SuppressWarnings("unchecked")
	protected T getSelected() {
		final int index = table.getSelectedRow();
		if (index == -1) {
			return null;
		} else {
			Long id = (Long)table.getModel().getValueAt(index, 0);
			return (T)dao.loadById(id, persistentEntity);
		}
	}

	private class EXCELAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {

			try {
				HSSFWorkbook workbook = manageWorkbook(getXLSFileName());

				JFileChooser jFileChooser = new JFileChooser();

				if (jFileChooser.showSaveDialog(table) == JFileChooser.APPROVE_OPTION) {
					excelService.saveWorkbook(workbook, jFileChooser.getSelectedFile());
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

		}
	}

	private class GotoLastAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			goLast();
		}
	}

	private class GotoFirstAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			goFirst();
		}
	}

	class GotoNextAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			goNext();
		}
	}

	class GotoPrevAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			goPrev();
		}
	}

	class CopyAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			copyRow();
		}
	}

	class DeleteAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {

			if (JOptionPane.showConfirmDialog(AbstractForm.this, "Da li zelite da obrisete?") == 0) {
				deleteRow();
			}
		}
	}

	class EditAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			editRow();
		}
	}

	class AddAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			addRow();
		}
	}

	class SearchAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			search();
		}
	}

	class RefreshAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			refreshView();
		}
	}

	class BackToFormAction implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			T selected = AbstractForm.this.getSelected();
			if (selected != null) {
				zoomFormBack(selected);
			}
		}
	}

	class SubstractLastAction implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent actionevent) {
			if (lastAdded != null) {
				if (JOptionPane.showConfirmDialog(AbstractForm.this, "Da li zelite da umanjite prethodno dodate vrednosti?") == 0) {
					ClassUtils.substract(fromDb, lastAdded);
					dao.update(fromDb);
					lastAdded = null;
					refreshView();
				}
			}
		}
	}

	protected void showInfoMSg(String msg) {
		JOptionPane.showMessageDialog(this, msg, "Obaveštenje", JOptionPane.INFORMATION_MESSAGE);
	}

	protected void showErrorMSg(String msg) {
		JOptionPane.showMessageDialog(this, msg, "Greska", JOptionPane.ERROR_MESSAGE);
	}

	public void updateStatus(List<T> items) {
		status.setText("Ukupno: " + items.size());
	}

	public T getNew() {
		try {
			return persistentEntity.newInstance();
		} catch (Exception e) {
			LOG.error(e);
			throw new IllegalStateException("Could not create instance of class", e);
		}
	}

	@SuppressWarnings("unchecked")
	protected List<T> getAll() {
		return cache.get(persistentEntity);
	}

	public static String createHtml(String label) {
		return "<html>" + label.replace(":", "<br/>") + "</html>";
	}

	public ExcelService getExcelService() {
		return excelService;
	}

	protected Class<T> getPersistentEntity() {
		return persistentEntity;
	}

	protected String getXLSFileName() {
		throw new IllegalStateException("name is not defined. override this method");
	}

	protected void selectInTable(AbstractPersistableEntity fromDb) {
		int rowCount = table.getModel().getRowCount();
		for (int i = 0; i < rowCount; i++) {
			Long id = (Long)table.getModel().getValueAt(i, 0);
			if (id.equals(fromDb.getId())) {
				Object[] row = fromDb.getRow();
				for (int k = 0; k < row.length; k++) {
					table.getModel().setValueAt(row[k], i, k);
				}
				break;
			}
		}
	}

	protected void hideExportButton() {
		if (excelButton != null) {
			excelButton.setVisible(false);
		}
	}
}
